/*
 * Copyright 2007 Ulf Ochsenfahrt <ulf@ofahrt.de>
 *
 * This file is part of mnemoj, a software transactional memory implementation
 * that is based on Project Darkstar.
 *
 * mnemoj is free software: you can redistribute it
 * and/or modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation and
 * distributed hereunder to you.
 *
 * Project Darkstar Server is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.ofahrt.mnemoj.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;

import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.NameNotBoundException;
import com.sun.sgs.app.ObjectNotFoundException;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TaskManager;

final class Transaction implements DataManager, TaskManager
{

private final Storage storage;
private final long startTime;
private boolean closed = false;
final HashMap<Long,ManagedEntry> lockedEntries = new HashMap<Long,ManagedEntry>();
final IdentityHashMap<ManagedEntry,ManagedObject> instancedEntries = new IdentityHashMap<ManagedEntry,ManagedObject>();
final IdentityHashMap<ManagedObject,Long> knownObjects = new IdentityHashMap<ManagedObject,Long>(); 

final HashMap<String,Binding> lockedBindings = new HashMap<String,Binding>();
final IdentityHashMap<Binding,ManagedReferenceImpl> instancedBindings = new IdentityHashMap<Binding,ManagedReferenceImpl>();

final ArrayList<TaskEntry> tasks = new ArrayList<TaskEntry>();

Transaction(Storage storage, long startTime)
{
	if (storage == null) throw new NullPointerException();
	this.storage = storage;
	this.startTime = startTime;
}

long getStartTime()
{ return startTime; }

private ManagedEntry getEntry(Long id)
{
	ManagedEntry entry = lockedEntries.get(id);
	if (entry != null) return entry;
	entry = storage.getEntry(id);
	lockedEntries.put(id, entry);
	ManagedObject result = entry.getCopy();
	instancedEntries.put(entry, result);
	knownObjects.put(result, entry.getId());
	return entry;
}

ManagedObject get(Long id)
{
	if (closed) throw new IllegalStateException();
	return getForUpdate(id);
}

ManagedObject getForUpdate(Long id)
{
	if (closed) throw new IllegalStateException();
	ManagedEntry entry = getEntry(id);
	ManagedObject result = instancedEntries.get(entry);
	if (result == null) throw new ObjectNotFoundException();
	return result;
}

@Override
public ManagedReferenceImpl createReference(ManagedObject object)
{
	if (closed) throw new IllegalStateException();
	Long id = knownObjects.get(object);
	if (id != null) return new ManagedReferenceImpl(id);
	ManagedEntry entry = storage.createEntry();
	lockedEntries.put(entry.getId(), entry);
	instancedEntries.put(entry, object);
	knownObjects.put(object, entry.getId());
	return new ManagedReferenceImpl(entry.getId());
}

@Override
public void removeObject(ManagedObject object)
{
	if (closed) throw new IllegalStateException();
	Long id = knownObjects.get(object);
	ManagedEntry entry = lockedEntries.get(id);
	instancedEntries.put(entry, null);
}

@Override
public void markForUpdate(ManagedObject object)
{
	if (closed) throw new IllegalStateException();
	// For now, all get operations get a write lock, so no change necessary here.
}

private Binding getBinding(String id)
{
	Binding binding = lockedBindings.get(id);
	if (binding == null)
	{
		binding = storage.getBinding(id);
		lockedBindings.put(binding.getId(), binding);
		instancedBindings.put(binding, binding.get());
	}
	return binding;
}

@Override
public <T> T getBinding(String id, Class<T> type)
{
	if (closed) throw new IllegalStateException();
	Binding binding = getBinding(id);
	ManagedReference ref = instancedBindings.get(binding);
	if (ref == null) throw new NameNotBoundException(id);
	return ref.get(type);
}

private Binding createBinding(String id)
{
	Binding binding = lockedBindings.get(id);
	if (binding == null)
	{
		binding = storage.createBinding(id);
		lockedBindings.put(binding.getId(), binding);
		instancedBindings.put(binding, binding.get());
	}
	return binding;
}

@Override
public void setBinding(String id, ManagedObject object)
{
	if (closed) throw new IllegalStateException();
	Binding binding = createBinding(id);
	instancedBindings.put(binding, object == null ? null : createReference(object));
}

@Override
public void removeBinding(String id)
{
	if (closed) throw new IllegalStateException();
	Binding binding = getBinding(id);
	instancedBindings.put(binding, null);
}

@Override
public String nextBoundName(String id)
{
	if (closed) throw new IllegalStateException();
	throw new UnsupportedOperationException();
}


private void doScheduleTask(Task task, long delay)
{
	if (delay < 0) throw new IllegalArgumentException("delay must be greater than or equal to 0");
	if (task instanceof ManagedObject) createReference((ManagedObject) task);
	TaskEntry entry = new TaskEntry(task, delay);
	tasks.add(entry);
}

public void scheduleTask(Task task)
{
	if (closed) throw new IllegalStateException();
	doScheduleTask(task, 0);
}

public void scheduleTask(Task task, long delay)
{
	if (closed) throw new IllegalStateException();
	doScheduleTask(task, delay);
}


void revert()
{
	if (closed) throw new IllegalStateException();
	storage.revert(this);
	closed = true;
}

void commit()
{
	if (closed) throw new IllegalStateException();
	try
	{
		storage.commit(this);
		closed = true;
	}
	finally
	{
		if (!closed) revert();
	}
}

}
